from app.services.driver import ThreadHTTPClient
import json
from typing import Optional, Callable
from app.core import exceptions, error_code
from functools import wraps
from abc import ABC, abstractmethod
from shared_data.robot import Mount, Point, Direction


def catch_exception(detail=""):
    def catch_exception_func(func: Callable):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            try:
                await func(*args, **kwargs)
            except Exception as e:
                raise exceptions.FlexHTTPClientException(
                    error_code=error_code.ErrorCode.UNKNOWN_ERROR, detail=f"{detail}, {e}")

        return wrapper

    return catch_exception_func


class FlexHttpClientBasic(ThreadHTTPClient, ABC):
    def __init__(self, host_name: str):
        super().__init__(base_url=f"http:{host_name}:3915", headers={"Opentrons_Version": "*"})
        self.run_id: Optional[str] = None

    @staticmethod
    async def assert_status_code(status_code: int, expect_code: int, detail: Optional[str] = None):
        if status_code != expect_code:
            raise exceptions.FlexHTTPClientException(
                error_code.ErrorCode.FLEX_HTTP_EXCEPTION,
                detail=f"{detail or 'Assert status code fail'}, status code {status_code}")

    @abstractmethod
    async def create_run(self):
        ...

    @abstractmethod
    async def delete_run(self):
        ...


class FlexHttpClientMaintenance(FlexHttpClientBasic):
    def __init__(self, host_name: str):
        super().__init__(host_name)
        self.robot_position: Optional[Point] = None

    @catch_exception(detail="Create maintenance run fail")
    async def create_run(self):
        """
        create maintenance run
        :return:
        """
        response = self.post('/maintenance_runs')
        status_code = response.status_code
        data = json.loads(response.text)
        if status_code == 201:
            self.run_id = data['data']['id']
        else:
            raise exceptions.FlexHTTPClientException(error_code.ErrorCode.FLEX_HTTP_EXCEPTION,
                                                     detail=f"create maintenance run fail, status code {status_code}")

    async def assert_run_id(self):
        if self.run_id is None:
            raise exceptions.FlexHTTPClientException(
                error_code.ErrorCode.FLEX_HTTP_EXCEPTION,
                detail=f"Run id not found.")

    @catch_exception(detail="Delete maintenance run fail")
    async def delete_run(self):
        await self.assert_run_id()
        url = f"/maintenance_runs/{self.run_id}"
        _data = {
            "data": {}
        }
        response = self.delete(url, data=_data)
        status_code = response.status_code
        await self.assert_status_code(status_code, 200, detail="delete maintenance run fail")

    @catch_exception(detail="Maintenance move to fail")
    async def move_to(self, mount: Mount, point: Point, speed: float = 567.8):
        """
        coordinate: ex: {"x": 115, "y": 100, "z": 342}
        """
        await self.assert_run_id()
        url = f"/maintenance_runs/{self.run_id}/commands?waitUntilComplete=true"
        if mount is Mount.LEFT:
            _key = 'leftZ'
        elif mount is Mount.RIGHT:
            _key = 'rightZ'
        else:
            raise exceptions.FlexHTTPClientException(error_code.ErrorCode.FLEX_HTTP_EXCEPTION, detail="Wrong Mount")
        _data = {
            "data": {
                "commandType": "robot/moveAxesTo",
                "params": {
                    "axis_map": {
                        'x': point.x,
                        'y': point.y,
                        _key: point.z
                    },
                    "speed": speed,
                }
            }
        }
        response = self.post(url, data=_data)
        status_code = response.status_code
        await self.assert_status_code(status_code, 201, detail="Move to fail")
        self.robot_position = point

    @catch_exception(detail="Maintenance move rel fail")
    async def move_rel(self, mount: Mount, direction: Direction, distance: float, speed: float = 567.8):
        if self.robot_position is None:
            raise exceptions.FlexHTTPClientException(
                error_code.ErrorCode.FLEX_HTTP_EXCEPTION,
                detail="Robot positon is null, probably move to first.")
        if direction == Direction.Up:
            new_point = self.robot_position + Point(z=+abs(distance))
        elif direction == Direction.Back:
            new_point = self.robot_position + Point(y=-abs(distance))
        elif direction == Direction.Down:
            new_point = self.robot_position + Point(z=-abs(distance))
        elif direction == Direction.Left:
            new_point = self.robot_position + Point(x=-abs(distance))
        elif direction == Direction.Right:
            new_point = self.robot_position + Point(x=abs(distance))
        elif direction == Direction.Forward:
            new_point = self.robot_position + Point(y=abs(distance))
        else:
            raise Exception("Wrong direction!")
        await self.move_to(mount, new_point, speed)


class FlexHttpClientHardware(FlexHttpClientBasic):
    def __init__(self, host_name: str):
        super().__init__(host_name)

    async def create_run(self):
        ...

    async def delete_run(self):
        ...

    @catch_exception(detail="Home fail")
    async def home(self):
        response = self.post("/robot/home", data={"target": "robot"})
        status_code = response.status_code
        await self.assert_status_code(status_code, 200, detail="Home fail")

    @catch_exception(detail="Home z fail")
    async def home_z(self, mount: Mount):
        response = self.post("/robot/home", data={"target": "pipette", "mount": mount.value})
        status_code = response.status_code
        await self.assert_status_code(status_code, 200, detail="Home z fail")


